package com.apes.framework.plugin.fsm.core;

import com.apes.framework.jpa.ApesBean;
import com.apes.framework.jpa.comment.Comment;
import com.apes.framework.jpa.repository.CustomRepository;
import com.apes.framework.jpa.spec.JpaDsl;
import com.apes.framework.plugin.esb.api.SimpleRequest;
import com.apes.framework.plugin.fsm.*;
import com.apes.framework.util.CacheUtil;
import com.apes.framework.util.JpaUtil;
import com.apes.framework.util.ReflectUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.util.StringUtils;
import org.squirrelframework.foundation.fsm.StateMachineData;

import java.io.Serializable;
import java.util.*;

/**
 * 功能：状态机引擎
 *
 * @author xul
 * @create 2020-08-27 11:37
 */
@Service
public class FsmEngine {

    @Autowired
    private FsmManager fsmManager;

    @Autowired
    private FsmFlowInstanceRepository fsmFlowInstanceRepository;

    @Autowired
    private FsmStateInstanceRepository fsmStateInstanceRepository;

    @Autowired
    private FsmFlowRepository fsmFlowRepository;

    public void fire(String fsmFlowId, Object entity) {
        FsmFlow fsmFlow = fsmFlowRepository.findOne(fsmFlowId);
        fire(fsmFlow, entity);
    }

    public void fire(FsmFlow fsmFlow, Object entity) {
        if (!(entity instanceof ApesBean)) {
            return;
        }
        String businessTypeId = JpaUtil.getDomainClass((ApesBean) entity).getSimpleName();
        if (!businessTypeId.equals(fsmFlow.getBusinessType().getId())) {
            return;
        }
        String businessId = JpaUtil.getIdToStr((ApesBean) entity);
        Map merge = getMerge(entity);
        merge.put("ctx", entity);
        String aggregateType = JpaUtil.getDomainClass((ApesBean) entity).getName();
        merge.put("aggregateType", aggregateType);
        merge.put("实体类型", aggregateType);
        fire(fsmFlow, businessId, merge);
    }

    /**
     * 对象转Map
     **/
    public static Map<String, Object> getMerge(Object source) {
        Map<String, Object> toMerge = new LinkedCaseInsensitiveMap<>();
        Arrays.stream(ReflectUtil.getPropertyDescriptors(source)).forEach(descriptor -> {
            Object value = ReflectUtil.getFieldValue(source, descriptor);
            if (value != null) {
                if (value instanceof Collection) return;
                if (value instanceof ApesBean) {
                    Object id = ReflectUtil.getFieldValue(value, "id");
                    if (id != null) value = id;
                }
            }
            toMerge.put(descriptor.getName(), value);
            Comment comment = ReflectUtil.getAnnotation(source, descriptor.getName(), Comment.class);
            if ("id".equals(descriptor.getName())) {
                toMerge.put("单号", value);
            } else if (comment != null) {
                toMerge.put(comment.value(), value);
            }
        });
        return toMerge;
    }

    private void fire(FsmFlow fsmFlow, String businessId, Map merge) {
        FsmFlowInstance fi = fsmFlowInstanceRepository.findFirstByFsmFlowAndBusinessKey(fsmFlow, businessId);
        if (fi == null) {
            fi = new FsmFlowInstance();
            fi.setFsmFlow(fsmFlow);
            fi.setBusinessType(fsmFlow.getBusinessType());
            fi.setBusinessKey(businessId);
            fi.setCurrentState(fsmFlow.findInitialState());
            FsmStateInstance fsi = getFsmFlowInstanceParent(fsmFlow, merge);
            if (fsi != null) {
                fi.setParent(fsi);
                fsi.setChild(fi);
                fsmStateInstanceRepository.saveAndFlush(fsi);
            }
            fi = fsmFlowInstanceRepository.saveAndFlush(fi);
        } else {
            if (fi.isFinished()) return;
        }
        FsmStateMachine fsm = fsmManager.createStateMachine(fi);
        fsm.setFlowInstance(fi);
        fsm.setContext(merge);
        fsm.start();
        fire(fsm);
    }

    private FsmStateInstance getFsmFlowInstanceParent(FsmFlow fsmFlow, Map merge) {
        String parentBusinessId = merge.containsKey("source") ? (String) merge.get("source") : null;
        String parentBusinessTypeId = merge.containsKey("sourceBusinessType") ? (String) merge.get("sourceBusinessType") : null;
        if (StringUtils.isEmpty(parentBusinessId)) {
            parentBusinessId = merge.containsKey("origin") ? (String) merge.get("origin") : null;
            parentBusinessTypeId = merge.containsKey("originBusinessType") ? (String) merge.get("originBusinessType") : null;
        }
        if (StringUtils.isEmpty(parentBusinessId)) return null;
        FsmStateInstance fsi = fsmStateInstanceRepository.findAll(
                JpaDsl.toCriteriaByEq("state.childFsmFlow", fsmFlow,
                        "fsmFlowInstance.businessType.id", parentBusinessTypeId,
                        "fsmFlowInstance.businessKey", parentBusinessId),
                new Sort(Sort.Direction.DESC, "id"))
                .stream().findAny().orElse(null);
        return fsi;
    }

    private void fire(FsmStateMachine fsm) {
        List<FsmEvent> events = new ArrayList<>();
        for (FsmEvent e : fsm.getCurrentRawState().getAcceptableEvents()) {
            if (!StringUtils.isEmpty(e.getWhenMvel()) && eval(e.getWhenMvel(), fsm.getContext())) { //StringUtils.isEmpty(fsmEvent.getWhenMvel()) ||
                events.add(e);
            }
        }

        if (events.size() == 0) {
            return;
        }
        if (events.size() > 1) {
            throw new RuntimeException(String.format("状态流：%s - %s有多个有效的触发事件！", fsm.getFlowInstance().getFsmFlow().getName(), fsm.getCurrentState().getName()));
        }
        FsmEvent fsmEvent = events.stream().findFirst().get();
        if (fire(fsm, fsmEvent)) {
            FsmFlowInstance fi = fsm.getFlowInstance();
            StateMachineData.Reader reader = fsm.dumpSavedData();
            fi.serialization(reader);
            fi.setCurrentState((FsmState) reader.currentState());
            fi = fsmFlowInstanceRepository.saveAndFlush(fi);
            fsm.setFlowInstance(fi);
            fire(fsm);
        }
    }

    /**
     * 触发指定事件
     **/
    private boolean fire(FsmStateMachine fsm, FsmEvent event) {
        if (fsm.canAccept(event)) {
            fsm.fire(event, fsm.getContext());
            return true;
        }
        return false;
    }

    /**
     * 执行表达式，有错误不抛异常，给默认值
     **/
    public static boolean eval(final String express, final Map<String, Object> context) {
        if (StringUtils.isEmpty(express)) {
            return true;
        }
        try {
            return (Boolean) CacheUtil.getEngine().execute(express, context);
        } catch (Exception e) {
            throw new RuntimeException("表达式：" + express + " 执行错误！" + e.getMessage());
        }
    }

    /**
     * 重新触发
     */
    public void resend(SimpleRequest request) {
        String classSimpleName = request.get("businessType");
        Class clazz = JpaUtil.getDomainClass(classSimpleName);
        if (clazz == null) {
            throw new RuntimeException("单据类不存在！");
        }
        CustomRepository repository = JpaUtil.getCustomRepository(clazz.getName());
        Serializable id = request.get("id");
        Object entity = repository.findOne(id);
        if (entity == null) {
            throw new RuntimeException("实例不存在！");
        }
        String fsmFlowId = request.get("fsmFlowId");
        FsmFlow fsmFlow = fsmFlowRepository.findOne(fsmFlowId);
        if (fsmFlow == null) {
            return;
        }
        fire(fsmFlow, entity);
    }
}
